package com.meter.reading.handler;

import com.alibaba.fastjson.JSONObject;
import com.meter.common.utils.DateUtils;
import com.meter.common.utils.HexUtils;
import com.meter.netty.channel.NettyChannel;
import com.meter.netty.utils.NettyUtil;
import com.meter.rabbitmq.service.RabbitService;
import com.meter.redis.service.RedisService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;

/**
 * tcp服务数据处理类
 */
@Component
@ChannelHandler.Sharable
@Slf4j
public class TcpServerHandler extends ChannelInboundHandlerAdapter {

	@Autowired
	private RedisService redisService;

	@Autowired
	private NettyChannel nettyChannel;

	@Autowired
	private RabbitService rabbitService;

	/**
	 * 活跃的、有效的通道
	 * 第一次连接成功后进入的方法
	 *
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		super.channelActive(ctx);
		String channelId = ctx.channel().id().asLongText();
		log.info("与通道号为" + channelId + "的客户端" + getRemoteAddress(ctx) + "建立连接，通道开启！");
		//将tcp客户端的channel信息保存
		nettyChannel.addChannel(ctx.channel());
	}

	/**
	 * 不活动的通道
	 * 连接丢失后执行的方法（client端可据此实现断线重连）
	 *
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		super.channelInactive(ctx);
		String channelId = ctx.channel().id().asLongText();
		//根据通道id获取采集器id
		if(redisService.hHasValue("sys-netty", channelId)){
			String deviceCode = redisService.hgetValue("sys-netty", channelId).toString();
			// 移除redis中保存的channel
			redisService.hdel("sys-netty", deviceCode);
			// 保存离线时间
			redisService.hset("offline-time", deviceCode, DateUtils.getTime());

			log.error("与通道号为" + channelId + "的客户端" + deviceCode + "失去连接，通道关闭！");
		}
		//移除tcp客户端的channel信息
		nettyChannel.removeChannel(ctx.channel());
		//断开连接
		ctx.disconnect();
	}

	/**
	 * 拿到传过来的msg数据，开始处理
	 *
	 * @param ctx
	 * @param msg
	 * @throws Exception
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		//获取管道id
		String channelId = ctx.channel().id().asLongText();
		String[] msgs = HexUtils.strChange(msg.toString(), 2);
		String start = msgs[0];
		String end = msgs[msgs.length - 1];
		if(start.equals("BB")){
			String deviceCode = msgs[2] + msgs[1];
			String cont = msgs[7];
			//这里设备注册
			if(cont.equals("11")) {
				if(!redisService.hHasValue("sys-netty", channelId)) {
					redisService.hset("sys-netty", deviceCode, channelId);
					// 保存上线时间
					redisService.hset("online-time", deviceCode, DateUtils.getTime());
				}
				String message = msgs[1] + " " + msgs[2] + " 00 00 00 00 11 00 00 00 00 00 00 ";
				String cs = HexUtils.hexCheckStr(message.split(" "));
				String receiveStr = "AA " + message + cs + " FF";
				nettyChannel.writeToClient(channelId, receiveStr, "心跳回复");
			} else if(cont.equals("01")) {
				log.info("接收到通道号为" + channelId + "客户端" + deviceCode + "的设备采集数据" + msg.toString());
				if(end.equals("F0")) {
					JSONObject jsonObject = new JSONObject();
					jsonObject.put("deviceCode", deviceCode);
					jsonObject.put("msg", msg.toString());
					rabbitService.sendWorkQueue("result_queue", jsonObject);
				} else {
					redisService.set("meter_data:" + channelId, msg.toString());
				}
			}
		} else if(redisService.hHasValue("sys-netty", channelId)){
			String deviceCode = redisService.hgetValue("sys-netty", channelId).toString();
			if(redisService.hasKey("meter_data:" + channelId)) {
				String data = redisService.get("meter_data:" + channelId);
				if(end.equals("F0")) {
					String message = data + msg.toString();
					redisService.del("meter_data:" + channelId);
					if(redisService.hasKey("reading_meter:" + deviceCode)) {
						redisService.del("reading_meter:" + deviceCode);
					}
					log.info("接收到通道号为" + channelId + "客户端" + deviceCode + "的完整设备采集数据" + msg.toString());
					JSONObject jsonObject = new JSONObject();
					jsonObject.put("deviceCode", deviceCode);
					jsonObject.put("msg", message);
					rabbitService.sendWorkQueue("result_queue", jsonObject);
				} else {
					String message = data + msg.toString();
					redisService.set("meter_data:" + channelId, message);
				}
			}
		}else{
			log.error("接收到通道号为" + channelId + "客户端" + getRemoteAddress(ctx) + "的错误信息" + msg.toString());
		}
	}

	/**
	 * 服务端接收客户端发送过来的数据结束之后调用
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		super.channelReadComplete(ctx);
	}

	/**
	 * 异常处理
	 *
	 * @param ctx
	 * @param cause
	 * @throws Exception
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		super.exceptionCaught(ctx, cause);
		String channelId = ctx.channel().id().asLongText();
		log.error("通道号为" + channelId + "的客户端" + getRemoteAddress(ctx) + "通道发生异常，数据无法解析：" + cause.getMessage());
	}

	/**
	 * 心跳机制，超时处理
	 *
	 * @param ctx
	 * @param evt
	 * @throws Exception
	 */
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		String channelId = ctx.channel().id().asLongText();
		if (evt instanceof IdleStateEvent) {
			IdleStateEvent event = (IdleStateEvent) evt;
			String deviceCode = "";
			if(redisService.hHasValue("sys-netty", channelId)) {
				deviceCode = redisService.hgetValue("sys-netty", channelId).toString();
			}
			if (event.state() == IdleState.READER_IDLE) {
				log.error("通道号为" + channelId + "的客户端" + deviceCode + "读超时，连接中断");
				ctx.disconnect();
			} else if (event.state() == IdleState.WRITER_IDLE) {
				log.error("通道号为" + channelId + "的客户端" + deviceCode + "写超时");
			} else if (event.state() == IdleState.ALL_IDLE) {
				log.error("通道号为" + channelId + "的客户端" + deviceCode + "超时");
			}
		}
	}

	/**
	 * 获取client对象：ip
	 *
	 * @param ctx
	 * @return
	 */
	private String getRemoteAddress(ChannelHandlerContext ctx) {
		InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
		return insocket.getAddress().getHostAddress();
	}

}
